package xorm

import (
	"fmt"
	"gitee.com/stevenchin/gin_study/xorm/initxorm"
	"github.com/gin-gonic/gin"
	"github.com/xormplus/builder"
	"math/rand"
	"net/http"
	"time"
)

/**
查询和统计数据。支持执行SQL与ORM两种方式查询数据
*/

// 一. ORM方式查询和统计数据。所有的查询条件不区分调用顺序，但必须在调用Get（默认展示一个查询出来的结果），Find（展示所有查询出来的结果），
// 	   Count（统计数据使用Count方法，Count方法的参数为struct的指针并且成为查询条件）, Iterate（提供逐条执行查询到的记录的方法，
// 	   他所能使用的条件和Find方法完全相同）, Rows（Rows方法和Iterate方法类似，提供逐条执行查询到的记录的方法，不过Rows更加灵活好用）
// 	   这几个函数之前调用。
// 同时需要注意的一点是，在调用的参数中，如果采用默认的SnakeMapper所有的字符字段名均为映射后的数据库的字段名，而不是field的名字
func Select(ctx *gin.Context) {

	param := ctx.Param("select")

	basi := new(basic)

	// 0. Id(interface{})：传入一个主键字段的值，作为查询条件
	if param == "id" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE `Id`=? LIMIT 1 [1]
		_, _ = initxorm.Engine.ID(1).Get(basi)
		ctx.JSON(http.StatusOK, basi)
	}

	// 1. Alias(string)：给Table设定一个别名
	//    Where(string, …interface{})：和SQL中Where语句中的条件基本相同，作为条件
	//    Get(bean interface{})：查询单条数据使用Get方法，在调用Get方法时需要传入一个对应结构体的指针
	if param == "alias" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` AS `b` WHERE (b.name = ?) LIMIT 1 [steven at 31]
		_, _ = initxorm.Engine.Alias("b").Where("b.name = ?", "steven at 31").Get(basi)
		ctx.JSON(http.StatusOK, basi)
	}
	if param == "where" {
		// 这里没有使用结构体，所以要指定表名（直接指定表名或使用表名对应的结构体指针）
		var id int64
		// SELECT `id` FROM `basic` WHERE (name = ?) LIMIT 1 [steven at 31]
		_, _ = initxorm.Engine.Table(&basic{}).Where("name = ?", "steven at 31").Cols("id").Get(&id)
		ctx.JSON(http.StatusOK, id)
	}

	// 2. And(string, …interface{})：和Where函数中的条件基本相同，作为条件
	if param == "and" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` AS `b` WHERE (b.name = ?) AND (b.age = ?) LIMIT 1 [steven at 31 20]
		_, _ = initxorm.Engine.Alias("b").Where("b.name = ?", "steven at 31").And("b.age = ?", 20).Get(basi)
		ctx.JSON(http.StatusOK, basi)
	}
	//SQL Builder的使用也可以达到此效果。如需了解更多SQL Builder相关知识可查阅：https://www.kancloud.cn/xormplus/xorm/212929
	if param == "and_builder" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` AS `b` WHERE b.name IN (?) AND b.age=? LIMIT 1 [steven at 31 20]
		_, _ = initxorm.Engine.Alias("b").Where(builder.In("b.name", "steven at 31")).And(builder.Eq{"b.age": 20}).Get(basi)
		ctx.JSON(http.StatusOK, basi)
	}

	// 3. Asc(…string)：指定字段名正序排序，可以组合
	//    Find(rowsSlicePtr interface{}, condiBean ...interface{})：查询多条数据。Find方法的第一个参数为slice的指针或Map指针，
	//    		即为查询后返回的结果，第二个参数可选，为查询的条件struct的指针
	basics := make([]*basic, 0) // 创建slice时初始化大小为0，这样避免初始值的问题
	if param == "asc" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` ORDER BY `id` ASC []
		_ = initxorm.Engine.Asc("id").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 4. Desc(…string)：指定字段名逆序排序，可以组合
	if param == "desc" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` ORDER BY `age` ASC, `name` DESC, `create_time` DESC []
		_ = initxorm.Engine.Asc("age").Desc("name", "create_time").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 5. Or(string, …interface{})：和Where函数中的条件基本相同，作为条件
	if param == "or" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE (((name = ?)) OR (name = ?)) [steven at 31 steven at 27]
		_ = initxorm.Engine.Where("name = ?", "steven at 31").Or("name = ?", "steven at 27").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 6. OrderBy(string)：按照指定的顺序进行排序
	if param == "orderBy" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` ORDER BY id desc []
		_ = initxorm.Engine.OrderBy("id desc").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 7. Select(string)：指定select语句的字段部分内容
	if param == "select" {
		// SELECT a.*, (select phone from user limit 1) as phone FROM `basic` AS `a` []
		_ = initxorm.Engine.Alias("a").Select("a.*, (select phone from user limit 1) as phone").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 8. In(string, …interface{})：某字段在一些值中，这里需要注意必须是[]interface{}才可以展开，由于Go语言的限制，
	// 	  []int64等不可以直接展开，而是通过传递一个slice
	if param == "in_1" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE `id` IN (?,?,?,?) [1 2 3 4]
		_ = initxorm.Engine.In("id", 1, 2, 3, 4).Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}
	if param == "in_2" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE `id` IN (?,?,?) [1 2 3]
		_ = initxorm.Engine.In("id", []int{1, 2, 3}).Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 9. Cols(…string)：只查询或更新某些指定的字段，默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段
	if param == "cols_select" {
		//  SELECT `id`, `name` FROM `basic` []
		if err := initxorm.Engine.Cols("id", "name").Find(&basics); err != nil {
			ctx.String(http.StatusInternalServerError, err.Error())
		}
		ctx.JSON(http.StatusOK, basics)
	}
	if param == "cols_update" {
		basi.Id = 1
		basi.Name = "我更新了"
		// UPDATE `basic` SET `Name` = ? WHERE `Id`=? [我更新了 1]
		_, _ = initxorm.Engine.ID(basi.Id).Cols("name").Update(basi)
		ctx.JSON(http.StatusOK, basi)
	}

	// 10. AllCols()：查询或更新所有字段。因为默认Update只更新非0，非”“，非bool的字段
	if param == "allCols_1" {
		basi.Id = 1
		basi.Name = "steven update1"
		// UPDATE `basic` SET `Name` = ?, `create_time` = ? WHERE `Id`=? [steven update1 1593251982 1]
		_, _ = initxorm.Engine.ID(basi.Id).Update(basi)
		ctx.String(http.StatusOK, "update success")
	}
	if param == "allCols_2" {
		basi.Id = 1
		basi.Name = "steven update2"
		// UPDATE `basic` SET `Name` = ?, `Password` = ?, `Age` = ?, `create_time` = ? WHERE `Id`=? [steven update2  0 1593252089 1]
		_, _ = initxorm.Engine.ID(basi.Id).AllCols().Update(basi)
		ctx.String(http.StatusOK, "update success")
	}

	// 11. MustCols(…string)：某些字段必须更新，一般与Update配合使用。因为默认Update只更新非0，非”“，非bool的字段
	if param == "mustCols" {
		basi.Id = 1
		basi.Name = "steven update3"
		// 设置 age 表字段必须更新
		//  UPDATE `basic` SET `Name` = ?, `Age` = ? WHERE `Id`=? [steven update3 0 1]
		_, _ = initxorm.Engine.ID(basi.Id).MustCols("age").Update(basi)
		ctx.String(http.StatusOK, "update success")
	}

	// 12. Omit(…string)：和cols相反，此函数指定排除某些指定的字段。TIPS：此方法和Cols方法不可同时使用
	if param == "omit_select" {
		// SELECT `Id`, `Name`, `Age`, `create_time` FROM `basic` []
		_ = initxorm.Engine.Omit("password").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}
	if param == "omit_update" {
		basi.Id = 1
		basi.Name = "这个名字设置也不更新"
		basi.Age = 100
		// UPDATE `basic` SET `Age` = ? WHERE `Id`=? [100 1]
		_, _ = initxorm.Engine.ID(basi.Id).Omit("name").Update(basi)
		ctx.String(http.StatusOK, "update success")
	}

	// 13. Distinct(…string)：按照参数中指定的字段去重结果
	if param == "distinct" {
		// SELECT DISTINCT `age` FROM `basic`
		_ = initxorm.Engine.Distinct("age").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 14. Table(nameOrStructPtr interface{})：传入表名称或者结构体指针，如果传入的是结构体指针，则按照设置的Mapper规则提取出表名
	if param == "table" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` []
		_ = initxorm.Engine.Table("basic").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}
	if param == "table_ptr" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` []
		_ = initxorm.Engine.Table(&basic{}).Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 15. Limit(int, …int)：限制获取的数目，第一个参数为条数；第二个参数表示开始位置，如果不传则为0
	if param == "limit" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` ORDER BY `id` DESC LIMIT 4 OFFSET 10 []
		_ = initxorm.Engine.Limit(4, 10).Desc("id").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 16. Top(int)：相当于Limit(int, 0)。貌似没用了，找不到此方法

	// 17. Join(string,interface{},string)：第一个参数为连接类型，当前支持INNER, LEFT, RIGHT , CROSS等， 第二个参数为
	// 	   string类型的表名，表对应的结构体指针或者为两个值的[]string，表示表名和别名， 第三个参数为连接条件。
	// 	   TIPS：个人不推荐使用sql语句替代业务逻辑，因为像在mybatis中，可能会将业务逻辑埋藏很深。而且数据库数据量上来后，
	//			 使用Join导致计算数据量增加，性能越来越差。如果需要可参考：https://www.kancloud.cn/xormplus/xorm/167102
	if param == "join" {
		// SELECT `user`.`id`, `b`.`name` FROM `basic` AS `b` LEFT JOIN `user` ON user.name = b.name []
		_ = initxorm.Engine.Alias("b").Join("LEFT", "user", "user.name = b.name").
			Cols("user.id, b.name").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 18. GroupBy(string)：Group By的参数字符串
	if param == "groupBy" {
		// SELECT `age` FROM `basic` GROUP BY age []
		_ = initxorm.Engine.GroupBy("age").Find(&basics)
		ctx.JSON(http.StatusOK, basics)
	}

	// 19. Having(string)：Having的参数字符串
	if param == "having" {
		// SELECT `age` FROM `basic` GROUP BY age HAVING count(age) > 1 []
		// SELECT count(*) FROM `basic` GROUP BY age HAVING count(age) > 1 []
		total, _ := initxorm.Engine.GroupBy("age").Having("count(age) > 1").FindAndCount(&basics)
		ctx.JSON(http.StatusOK, gin.H{"basics": basics, "total": total})
	}

	// 20. 临时开关方法之NoAutoTime()：如果此方法执行，则此次生成的语句中Created和Updated字段将不自动赋值为当前时间
	if param == "notAutoTime" {
		i := new(basic)
		i.Name = fmt.Sprintf("steven at %d", rand.Int31())
		i.Password = i.Name + "is password"
		i.Age = 20
		// basic 结构体的CreateTime 设置了属性定义`xorm:"created 'create_time'"`，即创建时间会自动赋值。这里设置创建时间将不会自动赋值
		// INSERT INTO `basic` (`Name`,`Password`,`Age`,`create_time`) VALUES (?,?,?,?) [steven at 1937158743 steven at 1937158743is password 20 0]
		_, _ = initxorm.Engine.NoAutoTime().Insert(i)
		ctx.String(http.StatusOK, fmt.Sprintf("insert id is %d", i.Id))
	}
	if param == "notAutoTime_hand" {
		i := new(basic)
		i.Name = fmt.Sprintf("steven at %d", rand.Int31())
		i.Password = i.Name + "is password"
		i.Age = 20
		i.CreateTime = time.Now().Unix()
		// 设置创建时间将不会自动赋值。通过手动设置创建时间来保存数据到数据库
		// INSERT INTO `basic` (`Name`,`Password`,`Age`,`create_time`) VALUES (?,?,?,?) [steven at 1498607704 steven at 1498607704is password 20 1593252801]
		_, _ = initxorm.Engine.NoAutoTime().Insert(i)
		ctx.String(http.StatusOK, fmt.Sprintf("insert id is %d", i.Id))
	}

	// 21. 临时开关方法之NoCache()：如果此方法执行，则此次生成的语句则在非缓存模式下执行

	// 22. 临时开关方法之NoAutoCondition()：禁用自动根据结构体中的值来生成条件
	//     TIPS：Get()方法中结构体的已有非空field自动成为查询的条件和前面的方法条件组合在一起查询
	if param == "noAutoCondition" {
		// SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE (name = ?) LIMIT 1 [steven]
		_, _ = initxorm.Engine.Where("name = ?", "steven").NoAutoCondition().Get(&basic{Id: 1})
		ctx.String(http.StatusOK, fmt.Sprintf("noAutoCondition。请查看控制台打印的sql"))
	}
	if param == "autoCondition" {
		//  SELECT `Id`, `Name`, `Password`, `Age`, `create_time` FROM `basic` WHERE (name = ?) AND `Id`=? LIMIT 1 [steven 1]
		_, _ = initxorm.Engine.Where("name = ?", "steven").Get(&basic{Id: 1})
		ctx.String(http.StatusOK, fmt.Sprintf("autoCondition。请查看控制台打印的sql"))
	}

	// 24. Sum系列方法。求和数据可以使用Sum, SumInt, Sums 和 SumsInt 四个方法，Sums系列方法的参数为struct的指针并且成为查询条件
	// 	   如有需要可参考：https://www.kancloud.cn/xormplus/xorm/175313

	// 25. Exist方法。检测记录是否存在，Get与Exist方法返回值都为bool和error，如果查询到实体存在，则Get方法会将查到的实体赋值给参数。
	// 	   如果你的需求是：判断某条记录是否存在，若存在，则返回这条记录，建议直接使用Get方法。如果仅仅判断某条记录是否存在，
	// 	   则使用Exist方法，Exist的执行效率要比Get更高
	if param == "exists_1" {
		basi.Name = "steven"
		b, _ := initxorm.Engine.Exist(basi)
		ctx.String(http.StatusOK, fmt.Sprintf("%s 是否存在：%t", basi.Name, b))
	}
	if param == "exists_2" {
		b, _ := initxorm.Engine.Exist(basic{
			Name: "steven",
			Age:  20,
		})
		ctx.String(http.StatusOK, fmt.Sprintf("steven，年龄20，是否存在：%t", b))
	}
	if param == "exists_3" {
		b, _ := initxorm.Engine.SQL("select * from basic where name = ?", "steven").Exist()
		ctx.String(http.StatusOK, fmt.Sprintf("steven，是否存在：%t", b))
	}
	if param == "exists_4" {
		_, _ = initxorm.Engine.SQL("select * from basic where name = ?", "steven").Get(basi)
		if basi != nil && basi.Name != "" {
			ctx.String(http.StatusOK, fmt.Sprintf("steven 存在"))
		} else {
			ctx.String(http.StatusOK, fmt.Sprintf("steven 不存在"))
		}
	}
}

// 二. 执行SQL查询。 执行指定的Sql语句，并把结果映射到结构体。有时，当选择内容或者条件比较复杂时，可以直接使用Sql执行一个SQL查询，
// 	   即Select命令。这里不做过多介绍，可参考：https://www.kancloud.cn/xormplus/xorm/167106
